
#include "zen_font.h"
#include "zen_image.h"
#include "zen_log.h"
#include "zen_exception.h"

#include "zen_truetype.h"

namespace Zen {
	
	class Font_i : public Font
	{
	public:
		std::shared_ptr<TrueType> mTrueType;
	public:
		std::shared_ptr<FontChar> getBitmap(uint32_t unicode, size_t height) override
		{
			try
			{
				return _getBitmap(unicode, height);
			}
			catch(std::exception & e)
			{
				LogE("exception: %s", e.what());
			}
			catch(...)
			{
				LogE("exception");
			}
			return nullptr;
		}

		std::shared_ptr<FontChar> _getBitmap(uint32_t unicode, size_t height)
		{
			if(height == 0) return nullptr;

			float scale = (float)height / m_font_info.height;

			if(scale > 2.f || scale < 0)
			{
				LogE("error font size, height:%u scale:%f", height, scale);
				return nullptr;
			}

			int g = mTrueType->getGlyphIndex((int)unicode);

			auto c = std::shared_ptr<FontChar>(new FontChar);
			
			c->unicode = unicode;

			auto bitmap = mTrueType->getGlyphBitmap(g, height);
			if(bitmap.pixels.size())
			{
				c->bitmap = std::move(bitmap.pixels);
				
				c->width = bitmap.w;
				c->height = bitmap.h;
			}
			c->bitmap_y = bitmap.y;
			c->bitmap_x = bitmap.x;
			c->x_advance = bitmap.x_advance;
			c->y_advance = m_font_info.lineHeight;
			return c;
		}

		void initFont_(const std::vector<uint8_t> &data) throw(...)
		{
			mTrueType->initFont(data);
			auto met = mTrueType->getFontMetrics();
			int limit = (1<<12);
			
			musts(abs(met.ascent) < limit, "invalid font ascent");
			musts(abs(met.lineGap) < limit, "invalid font line gap");
			musts(met.height > 0 && met.height < limit, "invalid font height");

			m_font_info.height = met.height;
			m_font_info.ascent = met.ascent;
			m_font_info.lineHeight = met.lineGap + m_font_info.height;
		}

		FontInfo const & getFontInfo() override {
			return m_font_info;
		}

		Font_i()
		{
			mTrueType = TrueType::Create();
		}
	protected:
		FontInfo m_font_info;
		bool m_is_good = false;
	};

	std::shared_ptr<Font> Font::Create(const std::vector<uint8_t> &data)
	{
		auto font = std::shared_ptr<Font_i>(new Font_i);
		font->initFont_(data);
		return font;
	}

	class FontBrush_i : public FontBrush {
	protected:
		std::shared_ptr<Font> m_font;
		std::map<uint32_t, std::shared_ptr<FontChar> > m_chars;
		FontBrushInfo m_info;
		size_t m_height;
	public:
		FontBrush_i(std::shared_ptr<Font> font, size_t height)
		{
			this->m_font = font;
			this->m_height = height;

			auto & finfo = font->getFontInfo();

			auto scale = (float)height / finfo.height;
			m_info.scale = scale;
			m_info.height = (float)height;
			m_info.ascent = finfo.ascent * scale;
			m_info.lineHeight = finfo.lineHeight * scale;
		}
		std::shared_ptr<FontChar> getCharBitmap(uint32_t unicode) override
		{
			auto iter = m_chars.find(unicode);
			if(iter != m_chars.end())
				return iter->second;

			auto c = m_font->getBitmap(unicode, (float)m_height);
			m_chars[unicode] = c;
			return c;
		}

		std::shared_ptr<Font> getFont() const override
		{
			return m_font;
		}

		const Zen::FontBrushInfo & getBrushInfo() const override {
			return m_info;
		}

		size_t getHeight() override {
			return m_height;
		}
	};

	std::shared_ptr<FontBrush> FontBrush::Create(std::shared_ptr<Font> font, size_t height)
	{
		musts(font, "invalid font(null)");
		auto fb = std::shared_ptr<FontBrush>(new FontBrush_i(font, height));
		return fb;

	}
	
	FontCache * FontCache::Get()
	{
		static auto single = new FontCache;
		return single;;
	}
}


/////////////////
/// Render
/////////////////

namespace Zen {
	
	/** image: grey format */
	static void sWriteChar(Image * image, FontChar * a, int x, int y)
	{
		int iw = (int)image->width();
		int ih = (int)image->height();
		
		x += a->bitmap_x; // image x
		y += a->bitmap_y;
		
		auto w = a->width; // used bitmap w
		auto h = a->height; // used bitmap h
		
		if(x + w <= 0 || x >= iw) return;
		if(y + h <= 0 || y >= ih) return;
		
		int ax = (x < 0)?-x:0; // bitmap x
		int ay = (y < 0)?-y:0; // bitmap y
		
		if(x + w >= iw) w = iw - x; // right out
		if(y + h >= ih) h = ih - y;
		w -= ax; // lefts out
		h -= ay;
		
		auto dst = image->data() + iw * (y + ay) + (x + ax);
		auto src = a->bitmap.data() + ay * a->width + ax;
		for(int i = 0; i < h; ++i)
		{
			for(int i = 0; i < w; ++i)
			{
				if(src[i] > dst[i]) dst[i] = src[i];
			}
			//			::memcpy(dst, src, w);
			dst += iw;
			src += a->width;
		}
	}
	
	static void sWriteUnderline(Image * image, int x, int y, int w, int underline)
	{
		underline = abs(underline);
		int iw = (int)image->width();
		int ih = (int)image->height();
		int top = y;
		int bot = y + underline;
		if(top > bot) std::swap(top, bot);
		if(top >= ih || bot <= 0) return; // y outside
		int lefts = x;
		int right = x + w;
		if(lefts > right) std::swap(lefts, right);
		if(lefts >= iw || right <= 0) return; // x outside
		if(lefts < 0) lefts = 0;
		if(right > iw) right = iw;
		if(top < 0) top = 0;
		if(bot >= ih) bot = ih;
		
		auto len = right - lefts; // >0
		auto src = image->data() + (top * iw + lefts);
		while(top++!=bot)
		{
			::memset(src, 0xff, len);
		}
	}
	
	class FontRender_i : public FontRender
	{
	protected:
		int m_out_w = 0;
		int m_out_h = 0;
		int m_out_top = 0; // highest pixel pos.
		
		std::vector<LineInfo> m_lines;
		
		std::shared_ptr<FontBrush> m_font_brush;
		
		std::u32string m_text;

		Style m_style;
		
	public:
		FontRender_i
		(std::shared_ptr<FontBrush> font,
		 std::u32string const & text,
		 Style const & style)
		{
			m_font_brush = font;
			m_text = text;
			m_style = style;
			
			this->initLinesAutoBreak();
			
			if(m_lines.size())
			{
				int text_height = m_font_brush->getBrushInfo().lineHeight;
				int last_top = (text_height + m_style.line_space) * (int)(m_lines.size() - 1);
				if(last_top < 0)
				{
					m_out_top = last_top;
					m_out_h = text_height - m_out_top;
				}
				else {
					m_out_top = 0;
					m_out_h = last_top + text_height;
				}
			}
			else m_out_h = 0;

			if(m_out_h < 0) m_out_h = 0;
			if(m_out_w < 0) m_out_w = 0;
		}

		virtual std::shared_ptr<Image> renderImage
		(size_t width, size_t height, RenderSetting const & set)
		override
		{
			if(width == 0) width = this->width();
			if(height == 0) height = this->height();

			if(width == 0 || height == 0) return nullptr;

			auto image = Image::Create(ePixel::Grey, width, height, set.fill);

			_writeImageH(image.get(), set.x_off, set.y_off, set.underline);

			return image;
		}
		
		virtual size_t width() override
		{
			return m_out_w;
		}
		
		virtual size_t height() override
		{
			return m_out_h;
		}
		virtual size_t lineCount() override
		{
			return m_lines.size();
		}

		virtual std::vector<LineInfo> const & getLines() override
		{
			return m_lines;
		}

	protected:
		
		void _newLine(LineInfo & _line)
		{
			int width = abs(_line.right_limit - _line.lefts_limit);
			if(width > m_out_w) m_out_w = width;
			
			m_lines.push_back(_line);
			
			_line = LineInfo();
		}
		
		void initLinesAutoBreak()
		{
			LineInfo _line;
			int x = 0;
			for(auto & i : m_text)
			{
				if(i == '\n')
				{
					_newLine(_line);
					continue;
				}
				auto ch = m_font_brush->getCharBitmap(i);
				if(!ch)
				{
					Zen::LogW("error char %d", (int)i);
					continue;
				}

				if(_line.chars.size())
				{
					int lefts = x + m_style.word_space + ch->bitmap_x;
					int right = lefts + ch->width;
					
					int lefts_limit = std::min(_line.lefts_limit, lefts);
					int right_limit = std::max(_line.right_limit, right);
					
					if(m_style.width_limit && right_limit - lefts_limit > m_style.width_limit)
					{
						_newLine(_line);
					}
					else
					{
						_line.lefts_limit = lefts_limit;
						_line.right_limit = right_limit;
						x += m_style.word_space + ch->x_advance;
						_line.chars.push_back(ch);
						continue;
					}
				}
				/// here _line.chars.size() == 0
				
				_line.lefts_limit = ch->bitmap_x;
				_line.right_limit = _line.lefts_limit + ch->width;
				x = ch->x_advance;
				
				_line.chars.push_back(ch);
			}
			_newLine(_line);
		}
		
		void _writeImageH(Image * image, int x_off, int y_off, int underline)
		{
			int y = y_off + m_font_brush->getBrushInfo().ascent - m_out_top;
			
			int w = (int)image->width();
			
			for(auto & line : m_lines)
			{
				int width = line.right_limit - line.lefts_limit;

				int line_x = int(m_style.alignment * float(w - width)) + x_off;
				int x = line_x - line.lefts_limit;
				for(auto i : line.chars)
				{
					sWriteChar(image, i.get(), x, y);
					x += i->x_advance + m_style.word_space;
				}
				if(underline) sWriteUnderline(image, line_x, y, width, underline);
				
				y += m_font_brush->getBrushInfo().lineHeight + m_style.line_space;
			}
		}
		
	};
	
	std::shared_ptr<FontRender> FontRender::Create
	(std::shared_ptr<FontBrush> brush,
	 std::u32string const & text,
	 Style const & style)
	{
		auto me = new FontRender_i(brush, text, style);
		return std::shared_ptr<FontRender>(me);
	}
}
